﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using FinPlusAssembler;
using FinPlusInterfaces;
using FinPlusCompCore;

namespace FinPlusCompProxy
{
    public class FinPlusCache: FinPlusComponent
    {
        [State(Test = true)]
        public override bool IsValid { get; set; }
        [State(Test = true)]
        public override string TestString { get { return Adapter.ToString(); } }
        public override IPods Adapter { get { return _adapter; } }
       
        private enum Params { LinkedCurve, Message, FailedTrade, Trade, Nominal, PayRec, Receiver, Payer, TempId, Instrument, CurveName, MarketName, CacheName }
        private readonly QLAnalytics _analytics;
        private readonly IFinPlusComp _tradeConn, _contraConn, _reportConn;
        private readonly IThrottle _throttle, _retry;
        private readonly IPods _adapter;
        private readonly IList<string> _exclude;
        private readonly IList<int> _contras;

        //construct
        public FinPlusCache(IFinPlusComp analytics, IFinPlusComp trades, IFinPlusComp contras, IFinPlusComp report, int throttle, int retry)
        {

            _analytics = (QLAnalytics)analytics;
            _tradeConn = trades;
            _contraConn = contras;
            _reportConn = report;
            _throttle = IOC.New<IThrottle>(throttle);
            _retry = IOC.New <IThrottle>(retry);
            _exclude = new List<string>() { "Counterparty", "Book", "IsClosed", "CollateralCurve" };
            _contras = new List<int>();
            _adapter = IOC.New<IPods>();
            
            _throttle.Start(UpdateTrades);
            _retry.Start(RetryTrades);
            _tradeConn.Adapter.PodUpdate += Trade_Update;
            IsValid = true;
            
            AddTrades();
            _retry.IsBusy = _throttle.IsBusy = false;
        }

        //common control interface
        public override void CmdExecute(IPod command) 
        {
            try
            {
                CmdCapture(command);

                switch ((Cmds)Enum.Parse(typeof(Cmds), command.Name, true))
                {
                    case Cmds.New:
                    case Cmds.Altered: Throttle((IPod)command.Value(Params.Trade.ToString()), false); break;
                    case Cmds.Removed: Throttle((IPod)command.Value(Params.Trade.ToString()), true); break;
                    default: throw new Exception(string.Format("cmd not recognised {0}", command.Name));
                }
            }
            catch (CmdExecuteException e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        public override void Dispose()
        {
            if (_tradeConn != null)
                _tradeConn.Dispose();
            if (_contraConn != null)
                _contraConn.Dispose();
        }

        //private
        private void Throttle(IPod trade, bool IsClosed)
        {
            trade.IsClosed = IsClosed;
            _throttle.Put(trade.Id.ToString(), trade);
        }

        private void UpdateTrades(IDictionary<string, object> trades)
        {
            try
            {
                foreach (IPod trade in trades.Values)
                {
                    if (trade.IsClosed)
                        Removed(trade);
                    else
                        Changed(trade);
                }

                _throttle.IsBusy = false;
            }
            catch(Exception e)
            {
                _throttle.IsBusy = IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        private void RetryTrades(IDictionary<string, object> trades)
        {
            try
            {
                foreach (IPod trade in trades.Values)
                    AddTrade(trade, false);

                _retry.IsBusy = false;
            }
            catch (Exception e)
            {
                _retry.IsBusy = IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        private void AddTrades()
        {
            try
            {
                foreach (var trade in _tradeConn.Adapter.Values)
                {
                    if(!trade.IsClosed)
                        AddTrade(trade, false);//add trade
                    
                    if(_contraConn != null && _contraConn.Adapter.ContainsKey(trade.Id))
                        AddTrade(_contraConn.Adapter[trade.Id], true);//add contra trade
                }
            }
            catch (Exception e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        private void AddTrade(IPod trade, bool contra)
        {
            try
            {
                if (contra) trade = Contra(trade);

                trade.Get(Params.MarketName.ToString(), string.Empty).Update(_analytics.MarketName, null, false);
                trade.Get(Params.CacheName.ToString(), string.Empty).Update(_analytics.CacheName, null, false);
                trade.Get(Params.TempId.ToString(), -1).Update(trade.Id, null, false);

                string res;
                lock (_analytics.QL)
                    res = _analytics.InvokeMethod(trade, Env.unknown, _exclude);

                var hasFailed = !res.Equals(trade.Id.ToString());
                if (hasFailed)
                    _retry.Put(trade.Id.ToString(), trade);
                else
                    _adapter.Update(trade, ChangeType.New);

                Report(trade, res, hasFailed);
            }
            catch (Exception e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        private void Report(IPod trade, string message, bool hasFailed)
        {
            if (_reportConn == null) return;

            if (hasFailed && !_reportConn.Adapter.ContainsKey(trade.Id))
                _reportConn.Adapter.Update(IOC.New<IPod>(Params.FailedTrade.ToString(), message, trade, trade.Id), ChangeType.New);
            else if (!hasFailed &&  _reportConn.Adapter.ContainsKey(trade.Id))
                _reportConn.Adapter.Update(_reportConn.Adapter[trade.Id], ChangeType.Removed);
        }

        private void Changed(IPod trade)
        {
            try
            {
                AddTrade(trade, false);//add trade
                if (_contraConn != null && _contraConn.Adapter.ContainsKey(trade.Id) && !_contras.Contains(trade.Id))
                    AddTrade(_contraConn.Adapter[trade.Id], true);//add contra trade
            }
            catch (Exception e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        private void Removed(IPod trade)
        {
            try
            {
                if (_contras.Contains(trade.Id))
                    AddTrade(Remove(trade), false);//flatten new trade
                else
                    AddTrade(trade, true);//add contra trade
            }
            catch (Exception e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }
        }

        private IPod Contra(IPod trade)
        {
            var contra = trade.Clone(-trade.Id);

            try
            {
                _contras.Add(trade.Id);
                var p = contra.Get(Params.PayRec.ToString());
                if (p != null)
                    p.Update(p.ToString().Equals(Params.Payer.ToString()) ? Params.Receiver.ToString() : Params.Payer.ToString());
                else
                    contra.Get(Params.Nominal.ToString()).Update(-(double)p.Object);
            }
            catch (Exception e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }

            return contra;
        }

        private IPod Remove(IPod trade)
        {
            try
            {
                trade.Get(Params.Nominal.ToString()).Update(0.0000000000001);
            }
            catch (Exception e)
            {
                IsValid = Level.Error.Log(Id, Config, e.Message, e);
            }

            return trade;
        }

        //cmds 
        private enum Cmds { New, Removed, Altered }

        //events
        private void Trade_Update(object s, BeanEventArgs a)
        {
            var pod = IOC.New<IPod>(a.ChangeType.ToString(), Id, User, -1);
            pod.Add(Params.Trade.ToString(), (IPod)s, User);
            CmdExecute(pod);
        }
    }
}
